package tk.winshu.shortestpath.view;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.winshu.shortestpath.algorithm.Dijkstra;
import tk.winshu.shortestpath.model.Node;
import tk.winshu.shortestpath.model.NodeData;
import tk.winshu.shortestpath.model.NodeStatus;
import tk.winshu.shortestpath.operate.*;
import tk.winshu.shortestpath.run.ICallback;
import tk.winshu.shortestpath.run.RunHandler;
import tk.winshu.shortestpath.util.ArrowDrawUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.List;

/**
 * @author winshu
 * @date 2015年2月2日
 */
public class CanvasPanel extends JPanel {

    private static final Logger log = LoggerFactory.getLogger(CanvasPanel.class);

    /**
     * 回调监听器
     */
    private ICallback runningCallback;

    private RunHandler runHandler;

    private NodeManager nodeManager;

    /**
     * 右键菜单
     */
    private JPopupMenu popupMenu;

    /**
     * 背景图
     */
    private Image backgroundImage;

    /**
     * 临时节点（绘制时用到）
     */
    private Node tempNode;

    /**
     * 是否有向图
     */
    private boolean isDirectedGraph;

    /**
     * 是否显示权重
     */
    private boolean isWeightVisible;

    /**
     * 是否循环运行
     */
    private boolean keepLoopRunning;

    private MouseListener mouseAdapter = new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            // 左键创建节点
            if (e.getButton() == MouseEvent.BUTTON1) {
                if (e.isShiftDown()) {
                    removeNode();
                } else {
                    createNode(e.getPoint());
                }
            }
            if (e.getButton() == MouseEvent.BUTTON3) {
                if (getFocusedNode() == null) {
                    return;
                }
                // 弹出右键菜单
                if (e.isPopupTrigger()) {
                    popupMenu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            // 左键，添加连线
            if (e.getButton() == MouseEvent.BUTTON1) {
                if (getFocusedNode() == null || !tempNode.isVisible()) {
                    return;
                }
                // 获取临时点所在节点，若不为空，则可以创建一条连线关系
                Node realNode = getNode(tempNode.getLocation());
                if (realNode != null && getFocusedNode() != realNode) {
                    nodeManager.execute(new AddLine(getFocusedNode(), realNode));
                }
            }

            // 右键，弹出菜单
            if (e.getButton() == MouseEvent.BUTTON3) {
                Node node = getNode(e.getPoint());
                if (node != null && e.isPopupTrigger()) {
                    popupMenu.show(e.getComponent(), e.getX(), e.getY());
                }
            }

            // 重置临时点
            tempNode.setVisible(false);
        }
    };

    private MouseMotionListener mouseMotionAdapter = new MouseMotionAdapter() {
        @Override
        public void mouseDragged(MouseEvent e) {
            // 按下 ctrl 键，移动节点
            if (e.isControlDown()) {
                moveNode(e.getPoint());
            } else {
                // 否则创建连线
                createTempLine(e.getPoint());
            }
        }

        // 鼠标移动时，更新焦点节点
        @Override
        public void mouseMoved(MouseEvent e) {
            if (nodeManager.setFocusedNode(getNode(e.getPoint()))) {
                repaint();
            }
        }
    };

    CanvasPanel() {
        tempNode = new Node();
        tempNode.setVisible(false);
        nodeManager = new NodeManager();
        runHandler = new RunHandler();
        createPopupMenu();

        // 监听鼠标释放动作，用于绘制节点和连线
        addMouseListener(mouseAdapter);
        // 监听鼠标轨迹动作，用于绘制直线和更新焦点节点
        addMouseMotionListener(mouseMotionAdapter);
    }

    /**
     * 清空画板
     */
    void cleanAll() {
        nodeManager.clear();
        tempNode.setVisible(false);
        repaint();
    }

    /**
     * 清空所有连线
     */
    void cleanLines() {
        nodeManager.execute(new CleanAllLines());
        repaint();
    }

    /**
     * 交换起点和终点
     */
    void exchange() {
        nodeManager.exchange();
        repaint();
    }

    /**
     * 在指定点创建节点
     */
    void createNode(Point2D point) {
        nodeManager.execute(new AddNode(point));
        repaint();
    }

    NodeData buildNodeData() {
        return nodeManager.buildNodeData();
    }

    int getNodeCount() {
        return nodeManager.getNodes().size();
    }

    boolean isRunnable() {
        return !runHandler.isRunning() && nodeManager.isReady();
    }

    void loadNodes(NodeData nodeData) {
        nodeManager.execute(new ImportNode(nodeData.getNodes()));
        nodeManager.setStartNode(nodeData.getStartNode());
        nodeManager.setEndNode(nodeData.getEndNode());
        repaint();
    }

    /**
     * 移动节点
     */
    void moveNode(Point2D point) {
        // 隐藏临时节点
        tempNode.setVisible(false);

        Node focusedNode = getFocusedNode();
        if (focusedNode != null) {
            // TODO 移动节点的功能需要完善了才能用
//            nodeManager.execute(new MoveNode(point, focusedNode, runner));
            focusedNode.setLocation(point);
            // runner 在当前节点上，一并移动
            repaint();
        }
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        // 绘制底图
        if (backgroundImage != null) {
            g.drawImage(backgroundImage, 0, 0, null);
        }
        nodeManager.drawNode((Graphics2D) g);
        drawAllLine((Graphics2D) g);
        runHandler.drawRunner((Graphics2D) g);
    }

    /**
     * 运行移动动画
     */
    boolean startAction() {
        Dijkstra dijkstra = new Dijkstra(isDirectedGraph);
        List<Node> nodePaths = dijkstra.find(nodeManager.getStartNode(), nodeManager.getEndNode());
        if (nodePaths.isEmpty()) {
            log.warn("找不到路径");
            stopAction();
            return false;
        }
        // 停止监听
        removeMouseListener(mouseAdapter);
        removeMouseMotionListener(mouseMotionAdapter);

        runHandler.startAction(nodePaths, keepLoopRunning, this::stopAction, this::repaint);
        return true;
    }

    /**
     * 是否有向图
     */
    void setDirectedGraph(boolean isDirectedGraph) {
        this.isDirectedGraph = isDirectedGraph;
        repaint();
    }

    /**
     * 是否循环运动
     */
    void setKeepLoopRunning(boolean isLoopRunning) {
        this.keepLoopRunning = isLoopRunning;
    }

    /**
     * 是否显示权重
     */
    void setWeightVisible(boolean weightVisible) {
        this.isWeightVisible = weightVisible;
        repaint();
    }

    /**
     * 注册运动事件
     */
    void setRunningCallback(ICallback callback) {
        this.runningCallback = callback;
    }

    /**
     * 停止正在运行的动作
     */
    void stopAction() {
        if (!runHandler.isRunning()) {
            return;
        }
        if (runningCallback != null) {
            runningCallback.onFinished();
        }
        runHandler.stopAction();
        addMouseListener(mouseAdapter);
        addMouseMotionListener(mouseMotionAdapter);
    }

    void setBackgroundImage(Image backgroundImage) {
        if (backgroundImage != null) {
            this.backgroundImage = backgroundImage;
            setBounds(0, 0, backgroundImage.getWidth(this), backgroundImage.getHeight(this));
            setPreferredSize(new Dimension(backgroundImage.getWidth(this), backgroundImage.getHeight(this)));
            repaint();
        }
    }

    void historyToPrevious() {
        if (nodeManager.historyToPrevious()) {
            repaint();
        }
    }

    void historyToNext() {
        if (nodeManager.historyToNext()) {
            repaint();
        }
    }

    private void createPopupMenu() {
        popupMenu = new JPopupMenu();

        // 设置起点
        JMenuItem setStartMenuItem = new JMenuItem("设为起点");
        setStartMenuItem.addActionListener(e -> markFocusedAsStartNode());
        popupMenu.add(setStartMenuItem);

        // 设置终点
        JMenuItem setEndMenuItem = new JMenuItem("设为终点");
        setEndMenuItem.addActionListener(e -> markFocusedAsEndNode());
        popupMenu.add(setEndMenuItem);

        popupMenu.addSeparator();

        // 移除节点
        JMenuItem evictNodeMenuItem = new JMenuItem("移除节点");
        evictNodeMenuItem.addActionListener(e -> removeNode());
        popupMenu.add(evictNodeMenuItem);

        // 移除连线
        JMenuItem evictLineMenuItem = new JMenuItem("移除连线");
        evictLineMenuItem.addActionListener(e -> removeLine());
        popupMenu.add(evictLineMenuItem);
    }

    /**
     * 标记焦点为终点
     */
    private void markFocusedAsEndNode() {
        if (nodeManager.markFocusedAsEndNode()) {
            repaint();
        }
    }

    /**
     * 设置焦点为开始节点
     */
    private void markFocusedAsStartNode() {
        if (nodeManager.markFocusedAsStartNode()) {
            repaint();
        }
    }

    /**
     * 移除焦点节点的连线
     */
    private boolean removeLine() {
        Node focusedNode = getFocusedNode();
        if (focusedNode == null) {
            return false;
        }
        nodeManager.execute(new CleanLines(focusedNode));
        repaint();
        return true;
    }

    /**
     * 移除焦点节点
     */
    private boolean removeNode() {
        Node focusedNode = getFocusedNode();
        if (focusedNode == null) {
            return false;
        }
        nodeManager.execute(new RemoveNode(focusedNode));
        repaint();
        return true;
    }

    private Node getFocusedNode() {
        return nodeManager.getFocusedNode();
    }

    /**
     * 根据位置获取节点，如果没找到则返回null
     */
    private Node getNode(Point2D point) {
        return nodeManager.getNode(point);
    }

    /**
     * 创建临时直线
     */
    private void createTempLine(Point2D point) {
        if (getFocusedNode() == null) {
            return;
        }

        Node realNode = getNode(point);
        tempNode.setLocation(realNode == null ? point : realNode.getLocation());
        tempNode.setVisible(true);

        repaint();
    }

    /**
     * 绘制连线
     */
    private void drawAllLine(Graphics2D g2) {
        for (Node node : nodeManager.getNodes()) {
            for (Node endNode : node.getEndNodes()) {
                // 运行中的路径显示红色
                g2.setColor(runHandler.isLineInPath(node.getIndex(), endNode.getIndex()) ? Color.RED : Color.GREEN);

                Point2D startPoint = node.getLocation();
                Point2D endPoint = endNode.getLocation();
                drawLine(g2, startPoint, endPoint);
                // 绘制权重
                if (isWeightVisible) {
                    g2.drawString(String.format("%.0f", node.distanceTo(endNode)),
                            (float) (startPoint.getX() + endPoint.getX()) / 2,
                            (float) (startPoint.getY() + endPoint.getY()) / 2);
                }
            }
        }
        // 绘制临时点到焦点的直线
        if (tempNode.isVisible() && getFocusedNode() != null) {
            Node targetNode = getNode(tempNode.getLocation());
            // 起始节点和目标节点不是同一个节点
            if (targetNode != getFocusedNode()) {
                g2.setColor(targetNode == null ? NodeStatus.TEMP.getColor() : Color.YELLOW);
                drawLine(g2, getFocusedNode().getLocation(), tempNode.getLocation());
            }
        }
    }

    private void drawLine(Graphics2D g2, Point2D a, Point2D b) {
        if (isDirectedGraph) {
            ArrowDrawUtil.draw(g2, a, b);
        } else {
            g2.draw(new Line2D.Double(a, b));
        }
    }
}
